Spring boot整合篇

[TOC]

1.优秀传送门

1-1.第一章送给GitHub仓库,多种框架整合实战案例

https://github.com/JeffLi1993/springboot-learning-example


公告

该套框架整合尽量以一个工程进行叠加演示,一般情况下后面的整合都包含前面的基础整合

(上面 1 仓库实战是各个框架分离,可以参考学习)


2.基础整合搭建ZK/Dubbo

Dubbo Spring Boot Starter 致力于简化 Dubbo 应用在 Spring Boot 环境中的开发,主要包括自动装配(Auto-Configure)、外部化配置(Externalized-Configuration)以及生产准备(Actuator)

思路

  1. 在Spring boot中整合ZK/Dubbo,可以将ZK抽出来放在parent中,这样子模块只需要考虑dubbo的服务配置即可
  2. 利用强大的注解功能,运用Dubbo的@Service做服务端注解,@Reference做消费端注解,快速整合Springboot+ZK+Dubbo

2-1.基础搭建传送门

https://www.bysocket.com/?p=1681

2-2.优秀开源项目

https://github.com/dubbo/dubbo-spring-boot-project

Springboot 多模块项目,整合了freemark,jsp,logback,mail,多数据源,mybatis,redis,docker,SSL等(待验证)

https://github.com/dony15/springboot-dubbox

2-3.配置详解

application.properties

简单基础配置

1
2
3
4
5
6
## Dubbo 服务提供者配置
spring.dubbo.application.name=provider/consumer
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.scan=org.spring.springboot.

spring.dubbo.application.name 应用名称

spring.dubbo.registry.address 注册中心地址

spring.dubbo.protocol.name 协议名称

spring.dubbo.protocol.port 协议端口

spring.dubbo.scan dubbo 服务类包目录

详细配置清单

#根据 starter 工程源码,可以看出 application.properties 对应的 Dubbo 配置类 DubboProperties 。

1
2
3
4
5
6
7
8
9
10
11
12
13
@ConfigurationProperties(prefix = "spring.dubbo")

public class DubboProperties {

private String scan;

private ApplicationConfig application;

private RegistryConfig registry;

private ProtocolConfig protocol;

}

包括了扫描路径、应用配置类、注册中心配置类和服务协议类 所以具体常用配置下扫描包路径:指的是 Dubbo 服务注解的服务包路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
## Dubbo 配置

# 扫描包路径

spring.dubbo.scan=org.spring.springboot.dubbo

应用配置类:关于 Dubbo 应用级别的配置

## Dubbo 应用配置

# 应用名称

spring.dubbo.application.name=xxx

# 模块版本

spring.dubbo.application.version=xxx

# 应用负责人

spring.dubbo.application.owner=xxx

# 组织名(BU或部门)

spring.dubbo.application.organization=xxx

# 分层

spring.dubbo.application.architecture=xxx

# 环境,如:dev/test/run

spring.dubbo.application.environment=xxx

# Java代码编译器

spring.dubbo.application.compiler=xxx

# 日志输出方式

spring.dubbo.application.logger=xxx

# 注册中心 0

spring.dubbo.application.registries[0].address=zookeeper:#127.0.0.1:2181=xxx

# 注册中心 1

spring.dubbo.application.registries[1].address=zookeeper:#127.0.0.1:2181=xxx

# 服务监控

spring.dubbo.application.monitor.address=xxx

这里注意多个注册中心的配置方式。下面介绍单个注册中心的配置方式。 注册中心配置类:常用 ZooKeeper 作为注册中心进行服务注册。

## Dubbo 注册中心配置类

# 注册中心地址

spring.dubbo.application.registries.address=xxx

# 注册中心登录用户名

spring.dubbo.application.registries.username=xxx

# 注册中心登录密码

spring.dubbo.application.registries.password=xxx

# 注册中心缺省端口

spring.dubbo.application.registries.port=xxx

# 注册中心协议

spring.dubbo.application.registries.protocol=xxx

# 客户端实现

spring.dubbo.application.registries.transporter=xxx

spring.dubbo.application.registries.server=xxx

spring.dubbo.application.registries.client=xxx

spring.dubbo.application.registries.cluster=xxx

spring.dubbo.application.registries.group=xxx

spring.dubbo.application.registries.version=xxx

# 注册中心请求超时时间(毫秒)

spring.dubbo.application.registries.timeout=xxx

# 注册中心会话超时时间(毫秒)

spring.dubbo.application.registries.session=xxx

# 动态注册中心列表存储文件

spring.dubbo.application.registries.file=xxx

# 停止时等候完成通知时间

spring.dubbo.application.registries.wait=xxx

# 启动时检查注册中心是否存在

spring.dubbo.application.registries.check=xxx

# 在该注册中心上注册是动态的还是静态的服务

spring.dubbo.application.registries.dynamic=xxx

# 在该注册中心上服务是否暴露

spring.dubbo.application.registries.register=xxx

# 在该注册中心上服务是否引用

spring.dubbo.application.registries.subscribe=xxx

服务协议配置类:

## Dubbo 服务协议配置

# 服务协议

spring.dubbo.application.protocol.name=xxx

# 服务IP地址(多网卡时使用)

spring.dubbo.application.protocol.host=xxx

# 服务端口

spring.dubbo.application.protocol.port=xxx

# 上下文路径

spring.dubbo.application.protocol.contextpath=xxx

# 线程池类型

spring.dubbo.application.protocol.threadpool=xxx

# 线程池大小(固定大小)

spring.dubbo.application.protocol.threads=xxx

# IO线程池大小(固定大小)

spring.dubbo.application.protocol.iothreads=xxx

# 线程池队列大小

spring.dubbo.application.protocol.queues=xxx

# 最大接收连接数

spring.dubbo.application.protocol.accepts=xxx

# 协议编码

spring.dubbo.application.protocol.codec=xxx

# 序列化方式

spring.dubbo.application.protocol.serialization=xxx

# 字符集

spring.dubbo.application.protocol.charset=xxx

# 最大请求数据长度

spring.dubbo.application.protocol.payload=xxx

# 缓存区大小

spring.dubbo.application.protocol.buffer=xxx

# 心跳间隔

spring.dubbo.application.protocol.heartbeat=xxx

# 访问日志

spring.dubbo.application.protocol.accesslog=xxx

# 网络传输方式

spring.dubbo.application.protocol.transporter=xxx

# 信息交换方式

spring.dubbo.application.protocol.exchanger=xxx

# 信息线程模型派发方式

spring.dubbo.application.protocol.dispatcher=xxx

# 对称网络组网方式

spring.dubbo.application.protocol.networker=xxx

# 服务器端实现

spring.dubbo.application.protocol.server=xxx

# 客户端实现

spring.dubbo.application.protocol.client=xxx

# 支持的telnet命令,多个命令用逗号分隔

spring.dubbo.application.protocol.telnet=xxx

# 命令行提示符

spring.dubbo.application.protocol.prompt=xxx

# status检查

spring.dubbo.application.protocol.status=xxx

# 是否注册

spring.dubbo.application.protocol.status=xxx

2-4.@Service 服务提供者常用配置

常用 @Service 配置的如下

  • version 版本

  • group 分组

  • provider 提供者

  • protocol 服务协议

  • monitor 服务监控

  • registry 服务注册

2-5.@Reference 服务消费者常用配置

常用 @Reference 配置的如下

  • version 版本

  • group 分组

  • timeout 消费者调用提供者的超时时间

  • consumer 服务消费者

  • monitor 服务监控

  • registry 服务注册

2-6.总结

A) 依赖问题

目前测试的是Spring boot整合的dubbo依赖

1
2
3
4
5
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>1.0.0</version>
</dependency>

注意该依赖中存在了dubbo和zk所有依赖包,不需要额外的配置zk,否则会引起jar包冲突,选择时可以通过插件查看

注意提供者如果使用事务,那么需要导入AOP依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
B)配置问题

注意ZK的注册地址

1
2
#ZK地址
spring.dubbo.registry.address=zookeeper://www.dony15.com:2181

注意服务包目录

1
spring.dubbo.scan=com.dony15.service

该目录的提供者和消费者并不需要一致,扫描的是本工程内对应的注解位置

1
2
3
4
5
#服务包目录-提供者
spring.dubbo.scan=com.dony15.service

#服务包目录-消费者
spring.dubbo.scan=com.dony15.dubbo
C)注解问题

@Service(version = “1.0.0”)和@Reference(version = “1.0.0”)

版本号可以写也可以不写,但是这两个注解注意是dubbo包下,并非spring包

D)解耦

可以将通用的接口和实体类抽离出来打成jar进行依赖,优点

  1. 提高复用性
  2. 避免springboot的按需依赖原则造成的过多引用问题
  3. 如果不这样做,注解方式的引用需要在提供者和消费者都建立服务接口,降低dubbo的实用性(每次调用服务都需要将服务内容复制一遍)
E)注解篇代码奉上Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful

https://github.com/dony15/my_springboot_code

###


3.整合Thymeleaf/Freemarker

3-1.代码

注解篇代码奉上Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful

https://github.com/dony15/my_springboot_code/tree/master/springboot-dubbo-mybatis-freemarker-thymeleaf

3-2.配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
#Themleaf配置
spring.thymeleaf.content-type=text/html
spring.thymeleaf.mode =LEGACYHTML5
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false

#配置静态资源路径
spring.mvc.static-path-pattern=/static/**

#DispatcherServlet 映射后缀(效果暂时没发现,并非伪静态技术)
server.sevlet-path=*.html


#freemarker模板
spring.freemarker.allow-request-override=false
spring.freemarker.cache=false
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false

3-3.总结

Thymeleaf和Freemarker可以共存,当然语法有区别,根据实际业务选择吧,整合基本没有难点,有时间可以了解一下他们的配置文件都是啥意思,百度很多,不留了

A)严谨问题

Thymeleaf对标签格式要求比较严谨,如果需要可以通过依赖jar进行自动补充(前段不一定写的很完整哦)

1
2
3
4
5
6
<!--ThymeLeaf代码补全-->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
B)伪静态思路

通过过滤器实现伪静态化,优化SEO,项目在不值得做大量静态页面时,可以使用

一时半会没整合好,就注释掉了,大概这个思路( • ̀ω•́ )✧

1
2
3
4
5
6
<!--伪静态化优化方案,未实现-->
<dependency>
<groupId>org.tuckey</groupId>
<artifactId>urlrewritefilter</artifactId>
<version>4.0.4</version>
</dependency>
C)依赖问题

Thymeleaf的依赖中已经存在spring-boot-starter-web的依赖

因此spring-boot-starter-web可以无情的去掉了

D)FreemarkerUtil使用问题

注意目录,如果写错了很尴尬哦.中间环节取不到值会提示异常,表达式值为null

E)前后端分离

如果你能参与项目的设计,那么通过/{page}进行较大程度的前后端分离,是个不错的点子哦,项目中有示例


4.整合 FastDFS+Nginx

本次整合会增加数据库字段,当然包括代码层的更新咯.url存图片来演示FastDFS功能

新增字段city_image存储url,演示是1张图,实际上在实体类中已经为多图扩展做了准备(数组切割)

4-1.代码

Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx

https://github.com/dony15/my_springboot_code/tree/master/3springboot-fastDFS-Nginx

4-2.配置文件

1
tracker_server=www.dony15.com:22122

不足

Nginx地址没有提出来,开发时应该提出来通用的,方案有很多,放在配置文件或者指定类或接口都可以

都没有做太多限制,比如图片类型,大小等等.可以进行各种判断过滤,这里只演示基础的功能实现

FastDFS只演示了增加,还缺少删和改哦,可以自行百度

4-3.总结

A)前段问题

这次演示没有使用富文本,可以更直接的去尝试细节(脑壳疼),

注意文件上传的类型,使用ajax的时候很容易前后不兼容,400或者406等

该方案是不依赖于form表单的ajax 详细见源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<input  type="file" alt="插入图片" id="uploadFile"name="uploadFile" />
-----------------------------------------------------
/*
* 图片上传
* 注意如果不加processData:false和contentType:false会报错
*/
$("#uploadFile").change(function () {
var imageForm = new FormData();
imageForm.append("uploadFile", $("#uploadFile").get(0).files[0]);
$.ajax({
type: 'POST',
url: "/insertImage",
data: imageForm,
processData: false, // 告诉jQuery不要去处理发送的数据
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
success: function (data) {
var result = JSON.parse(data);
if (result.error==1) {
alert(result.message)
}else{
$("#image_echo").attr("src", result.url);
$("#cityImage").attr("value", result.url);
}
},
error: function () {
alert("上传失败")
}
});
})
B)基础逻辑

基础的逻辑,文件上传到FastDFS,通过回调获取URI

再和Nginx的地址拼接成完整的URL响应给前段

前段拿到响应的URL和其他数据一起存进数据库

5.整合Redis

5-1.代码

Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx+Redis

https://github.com/dony15/my_springboot_code/tree/master/springboot4-redis

总结

A)依赖问题

注意redis需要和jackson一起,否则异常

1
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'functionDomainRedisTemplate' defined in class path resource [com/dony15/config/RedisConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.core.RedisTemplate]: Factory method 'functionDomainRedisTemplate' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper

我们可以看到这里的提示,缺少jacksion中的ObjectMapper

1
nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper

因此依赖应该这样配套使用

1
2
3
4
5
6
7
8
9
10
<!-- 整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

当然,FastJSON也是需要的,主要用在处理业务的时候,选择怎么存的策略转换,比如List,Map,数组等的处理选择上

B)配置-单机版

Springboot整合redis,需要注意RedisTemplate的注入,通过配置类来实现

将RedisUtil注入封装RedisTemplate

调用RedisUtil通过自动装配或者@Resource注入即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#Redis
#Matser的ip地址
redis.host=www.dony15.com
#端口号
redis.port=6379
#如果有密码
redis.password=
#客户端超时时间单位是毫秒 默认是2000
redis.timeout=20000

#最大空闲数
redis.maxIdle=300
#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal
#redis.maxActive=600
#控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性
redis.maxTotal=1000
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
redis.maxWaitMillis=10000
#连接的最小空闲时间 默认1800000毫秒(30分钟)
redis.minEvictableIdleTimeMillis=300000
#每次释放连接的最大数目,默认3
redis.numTestsPerEvictionRun=1024
#逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
redis.timeBetweenEvictionRunsMillis=30000
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
#在空闲时检查有效性, 默认false
redis.testWhileIdle=true

6.整合Logback全局异常处理

个人理解目前Logback仍为主流的异常处理工具,但是配置细粒度较为麻烦,并且难以分析,对于中小项目比较实用;

对于长期的中大型soa项目,建议添加Cat检测,更加细粒度控制大部分细节,使用也相对简单方便,如:

  1. 分布式细粒度实时监控
  2. 故障快速发现
  3. 系统问题分析
  4. Cat报表展示消息类型
  5. Transaction
  6. Event
  7. Heartbeat
  8. Metric
  9. Trace
  10. 各种埋点
  11. 丰富的模块警告通知/多种通知方式

Cat待更新,在新篇章介绍

6-1.代码

Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx+Redis+Logback

https://github.com/dony15/my_springboot_code/tree/master/springboot5-logback

6-2.配置

A)、Logger、appender及layout

Logger作为日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。

Appender主要用于指定日志输出的目的地,目的地可以是控制台、文件、远程套接字服务器、 MySQL、 PostreSQL、 Oracle和其他数据库、 JMS和远程UNIX Syslog守护进程等。

Layout 负责把事件转换成字符串,格式化的日志信息的输出。

B)、logger context

各个logger 都被关联到一个 LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各 logger。其他所有logger也通过org.slf4j.LoggerFactory 类的静态方法getLogger取得。 getLogger方法以 logger 名称为参数。用同一名字调用LoggerFactory.getLogger 方法所得到的永远都是同一个logger对象的引用。

C)、有效级别及级别的继承

Logger 可以被分配级别。级别包括:TRACE、DEBUG、INFO、WARN 和 ERROR,定义于 ch.qos.logback.classic.Level类。如果 logger没有被分配级别,那么它将从有被分配级别的最近的祖先那里继承级别。root logger 默认级别是 DEBUG。

D)、打印方法与基本的选择规则

打印方法决定记录请求的级别。例如,如果 L 是一个 logger 实例,那么,语句 L.info(“..”)是一条级别为 INFO 的记录语句。记录请求的级别在高于或等于其 logger 的有效级别时被称为被启用,否则,称为被禁用。记录请求级别为 p,其logger的有效级别为 q,只有则当 p>=q时,该请求才会被执行。

该规则是 logback 的核心。级别排序为: TRACE < DEBUG < INFO < WARN < ERROR

Logback默认配置的步骤

(1). 尝试在 classpath 下查找文件 logback-test.xml;

(2). 如果文件不存在,则查找文件 logback.xml;

(3). 如果两个文件都不存在,logback 用 Bas icConfigurator 自动对自己进行配置,这会导致记录输出到控制台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="D:/temp/log" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />

<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>

<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
<!--日志异步到数据库 -->
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<!--日志异步到数据库 -->
<!-- DriverManagerConnectionSource不支持DataSource -->
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<!-- DataSourceConnectionSource支持DataSource,但是没整合上,这里有区别,使用时需要注意 -->
<!--<connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">-->
<!--连接池 -->
<!--<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">-->
<!--<dataSource class="com.alibaba.druid.pool.DruidDataSource">-->
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://localhost:3306/test01?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false</url>
<user>root</user>
<password>68835230</password>
<!--</dataSource>-->
</connectionSource>
</appender>

<!-- 数据库日志输出级别 -->
<root level="INFO">
<!--<appender-ref ref="STDOUT" />-->
<appender-ref ref="DB" />
</root>
</configuration>

6-3.建表语句

如果输出到数据库,那么需要先建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;

BEGIN;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT;


BEGIN;
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;


BEGIN;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;

6-5.加入全局异常处理器即可使用

这里整合了ajax请求(需要ajax请求工具类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.dony15.exception;

import com.alibaba.fastjson.JSON;
import com.dony15.utils.AjaxResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
* 统一的错误处理页面
*/
public class ExceptionHandler implements HandlerExceptionResolver {
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {

logger.error(ex.getMessage(),ex);
//ajax 请求
if (request.getHeader("x-requested-with") != null && request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) { //如果是ajax请求响应头会有x-requested-with
AjaxResponse ajaxResponse = new AjaxResponse();
ajaxResponse.setThrowable(ex);
response.reset();
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e) {
logger.error(e.getMessage(),e);
}
out.print(JSON.toJSONString(ajaxResponse));
out.flush();

return null;
}else {//非ajax请求跳转登陆页

ModelAndView mv = new ModelAndView();

Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if ("debug".equals(cookies[i].getName())) {
StackTraceElement[] stackTraces = ex.getStackTrace();

StringBuilder stackTraceStr = new StringBuilder();
stackTraceStr.append(ex.toString())
.append("<br>");
for (int j = 0; j < stackTraces.length; j++) {
stackTraceStr.append(stackTraces[j].toString())
.append("<br>");
}

mv.getModel().put("stackTraces", stackTraceStr);
}
}
}

mv.setViewName("error.html");
return mv;
}
}
}

6-6.总结

A)依赖

依赖没有问题,spring-boot默认首先支持logback,不需要引入,详细关系可以查看关系图

B)配置

配置中需要注意的是,如果输出到数据库,那么除了配置文件外,需要在数据库中建好表哦

测试logback-test.xml

生产logback.xml


7.整合Quartz

7-1.配置组成

以下为Spring boot 方式的配置,不依赖于任何配置文件

  1. JobFactory 配置类:Job注入到Spring中管理,否则无法与Spring中的Bean交互
  2. QuartzConfig 配置类:Schedulder注入到Spring中管理
  3. QuartzManager 工具管理类:封装调用Quartz的方法,如添加/修改/删除/查看
  4. SelectCityJob Job类:任务类,处理业务逻辑的地方

注意此处配置的注入,实际上是将Job和Schedulder的工厂交给Spring管理,
因为Quartz本身具有自己的容器,而自身的容器和Spring的容器互相没有关联,导致Bean无法沟通

可参考该文章:

https://blog.csdn.net/xiaobuding007/article/details/80455187

①个人仓库中代码集成演示:
https://github.com/dony15/my_springboot_code/tree/master/springboot6-quartz%20X
该演示是单独Quartz的集成,特意打穿MVC架构使用,问题和理解↓↓↓

7-2.原理

假设: 我们需要通过CMS界面来定时执行某个任务

中的集成实际上存在问题,首先我们知道Quartz底层调用Object.wait()方法来阻塞实现

那么使用Quartz就必须异步实现,否则将会影响程序的正常运行

该演示中的集成恰好没有实现异步,我们一起来看后果是什么:

首先:如果我们在Controller调用Job的方法,那么会抛出TimeOut超时,其实很容易理解,

Object.wait()阻塞下是拿不到返回值的,该方法执行一段时间后,因为Controller中的请求本身有设定超时时间,

时间到了自然超时(防止死锁和同步的问题,spring本身的优化方案)

超时情况下,我们的Quartz实际上是生效了的,而且会生效两次,这是SpringMVC本身的机制,超时时仍然会刷新该方法

这个时候的Quartz会被再次执行,导致一个无关紧要的异常提示:该job已经存在

1
2
3
4
5
6
7
8
9
10
//错误示范
public String getCityByName(String cityName) {
Map<String, Object> params = new HashMap<>();
params.put("cityName", cityName);
//try..catch省略
quartzManager.addJob("getCityN", "cityJ",
"getCityT", "cityT",
SelectCityJob.class, "0/3 * * * * ?", params);
return "SUCCESS";
}

解决这两个问题我进行了如下尝试

  1. 不调用需要返回值
  2. 提前进行任务判断
  3. (Job中设置@DisallowConcurrentExecution 禁止并发)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//初次解决方案
public void getCityByName(String cityName) {
Boolean exists = quartzManager.notExists("getCityT", "cityT");
Map<String, Object> params = new HashMap<>();
params.put("cityName", cityName);
if (!exists){
//如果存在该任务,那么不操作
System.out.println("存在该任务哦,可以尝试移除");
}else{
//如果不存在,那么创建该任务
quartzManager.addJob("getCityN", "cityJ",
"getCityT", "cityT",
SelectCityJob.class, "0/3 * * * * ?", params);
}
}

实际上该方案只能解决第二个异常提示:该job已存在

对于SpringMVC超时问题仍然无法解决,那么问题的根源又回到了一开始,为什么Quartz要使用异步来处理

我们知道SpringMVC本身有一套完善的请求流程(13步),但是我们在handler中的业务操作同步阻塞,

导致后续的流程需要超时后才能执行

虽然以上两个异常提示都不会终止程序,也不会终止业务的实现,但是等待超时的时间和毫无意义的异常仍然不利于我们的开发

同理,在此代码中,Quartz和dubbo也会发生同步超时的状态

因此,我们可以重新屡一下

我们需要定时做些事情–>选择定时框架Quartz(扔进大后方)–>Controller难以做到异步通知(X)–>选择MQ消息队列通知(√)–>完美ヾ(゚∀゚ゞ)

7-3.正确代码使用

修改后的完整代码

Thymeleaf+Freemarker+Spring boot+Dubbo+ZK+JDBC+AOP+Mybatis+Restful+FastDFS+Nginx+Redis+Logback+Quartz+ActiveMQ

https://github.com/dony15/my_springboot_code/tree/master/springboot7-quartz%20%E2%88%9A-ActiveMQ

加入MQ

ActiveMQ

注意:序列化的对象不可以直接用来做ActiveMQ的消息,需要添加配置允许

1
2
3
4
spring.activemq.packages.trust-all=true   //允许所有

此处百度 //允许指定序列化类
详细可参考ActiveMQ文章

核心:

  1. ActiveMQ配置文件
  2. ActiveProvider
  3. ActiveConsumer

7-4.结合数据库

本次数据库结合并非持久化MQ消息,只是持久化Quartz的任务

假装结合一下,剩下的就是普通的业务逻辑了,说明书代替就好

7-5.ActiveMQ消息持久化到数据库

http://topmanopensource.iteye.com/blog/1066383

7-6.RocketMQ

资源传送门

https://blog.csdn.net/zhangll_2008/article/details/78657177

在MQ文章中记录个人总结

https://dony15.github.io/2018/08/24/MQ%E5%9F%BA%E7%A1%80%E4%B8%8E%E8%BF%90%E7%94%A8/

7-7.整合Quartz总结

A)结构问题

注意其本身需要异步实现,否则基本上会出现超时问题

本身具有自己的容器,该容器与Spring容器无关,需要配置注入到Spring管理,才能使用Spring中的bean

B)位置

建议放在大后方,还是异步处理的问题,最好不要与其他框架同步